package lgrpc

import (
	"context"
	"errors"
	"reflect"
	"strconv"

	context2 "lgrpc/context"
	"lgrpc/message"
	"lgrpc/serializer"
	"lgrpc/service"
	"lgrpc/tcp"
)

type Proxy interface {
	Invoke(ctx context.Context, req *message.Request) (*message.Response, error)
}

func InitProxy(service service.Service, client *tcp.Client) error {
	return setFuncField(service, client, client.Serializer())
}

func setFuncField(service service.Service, proxy Proxy, serializer serializer.Serializer) error {
	if service == nil {
		return ServiceNilError
	}

	typ := reflect.TypeOf(service)
	val := reflect.ValueOf(service)
	if typ.Kind() != reflect.Pointer || typ.Elem().Kind() != reflect.Struct {
		return ServiceTypeError
	}

	val = val.Elem()
	typ = typ.Elem()
	for i := 0; i < typ.NumField(); i++ {
		fieldVal := val.Field(i)
		fieldTyp := typ.Field(i)

		if fieldVal.CanSet() {
			fn := func(args []reflect.Value) (results []reflect.Value) {
				retVal := reflect.New(fieldTyp.Type.Out(0).Elem())
				reqData, err := serializer.Encode(args[1].Interface())
				if err != nil {
					return []reflect.Value{retVal, reflect.ValueOf(err)}
				}

				ctx := args[0].Interface().(context.Context)

				var meta = make(map[string]string, 2)
				if context2.IsOneway(ctx) {
					meta["one-way"] = "true"
				}
				if deadline, ok := ctx.Deadline(); ok {
					meta["deadline"] = strconv.FormatInt(deadline.UnixMilli(), 10)
				}

				req := message.NewRequestBuilder().
					SetServiceName(service.Name()).
					SetMethodName(fieldTyp.Name).
					SetData(reqData).
					SetSerializer(serializer.Code()).
					SetMeta(meta).
					Build()

				resp, err := proxy.Invoke(ctx, req)
				if err != nil {
					return []reflect.Value{retVal, reflect.ValueOf(err)}
				}

				var retErr error
				if len(resp.Error) > 0 {
					retErr = errors.New(string(resp.Error))
				}

				if len(resp.Data) > 0 {
					err = serializer.Decode(resp.Data, retVal.Interface())
					if err != nil {
						// 反序列化的 error
						return []reflect.Value{retVal, reflect.ValueOf(err)}
					}
				}

				var retErrVal reflect.Value
				if retErr == nil {
					retErrVal = reflect.Zero(reflect.TypeOf(new(error)).Elem())
				} else {
					retErrVal = reflect.ValueOf(retErr)
				}

				return []reflect.Value{retVal, retErrVal}

			}
			fnVal := reflect.MakeFunc(fieldTyp.Type, fn)
			fieldVal.Set(fnVal)
		}
	}
	return nil
}
